﻿/**
 * Copyright (C) 2012 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Spi;
using NGuice.Inject.Internal.Util;
using System.Reflection;

namespace NGuice.Inject.Internal
{
    public delegate void Run();

    public delegate void CreationNotify(Errors errors);

    /// <summary>
    /// 处理<code>Binder.Bind()</code>和<code>Binder.BindConstant()</code>元素的处理器
    /// </summary>
    internal class BindingProcessor : AbstractProcessor
    {
        //private readonly IList<CreationListener> creationListeners = new List<CreationListener>();
        internal event CreationNotify creation;
        internal readonly Initializer initializer;
        internal readonly IList<Run> uninitializedBindings = new List<Run>();

        internal BindingProcessor(Errors errors, Initializer initializer)
            : base(errors)
        {
            this.initializer = initializer;
        }

        public override bool Visit<T>(IBinding<T> command)
        {
            if (typeof(AsyncVoid).Equals(command.Key.GetType().GetGenericArguments()[0]))
            {
                if (command is IProviderInstanceBinding<T> && ((IProviderInstanceBinding<T>)command).ProviderInstance is ProviderMethod<T>)
                {
                    errors.VoidProviderMethod();
                }
                else
                {
                    errors.MissingConstantValues();
                }
                return true;
            }

            if (command.Key.GetType() == typeof(IProvider<>))
            {
                errors.BindingToProvider();
                return true;
            }

            ValidateKey(command.Source, command.Key);
            command.AcceptTargetVisitor(new BindingTargetVisitor<T>(this, command));
            return true;
        }

        /// <summary>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="AsyncVoid"></typeparam>
        internal class BindingTargetVisitor<T> : IBindingTargetVisitor<T, AsyncVoid>
        {
            private BindingProcessor processor;
            private IBinding<T> command;
            private Scoping scoping;

            public BindingTargetVisitor(BindingProcessor processor, IBinding<T> command)
            {
                this.processor = processor;
                this.command = command;
                scoping = Scoping.MakeInjectable(((BindingImpl<T>)command).Scoping, processor.injector, processor.errors);
            }

            public AsyncVoid Visit(IConstructorBinding<T> binding)
            {
                try
                {
                    ConstructorBindingImpl<T> onInjector = ConstructorBindingImpl<T>.Create(processor.injector, command.Key, binding.Constructor,
                                                           command.Source, scoping, processor.errors, false);
                    ScheduleInitialization(onInjector);
                    processor.PutBinding(onInjector);
                }
                catch (ErrorsException e)
                {
                    processor.errors.Merge(e.Errors);
                    processor.PutBinding(processor.InvalidBinding<T>(processor.injector, command.Key, command.Source));
                }
                return null;
            }

            public AsyncVoid Visit(IInstanceBinding<T> binding)
            {
                ISet<InjectionPoint> injectionPoints = binding.InjectionPoints;
                T instance = binding.Instance;
                Initializable<T> ini = processor.initializer.RequestInjection(processor.injector, instance, command.Source, injectionPoints);
                ConstantFactory<T> factory = new ConstantFactory<T>(ini);
                InternalFactory<T> scopedFactory = Scoping.Scope(command.Key, processor.injector, factory, command.Source, scoping);
                processor.PutBinding(new InstanceBindingImpl<T>(processor.injector, command.Key, command.Source, scopedFactory, injectionPoints, instance));
                return null;
            }

            public AsyncVoid Visit(IProviderInstanceBinding<T> binding)
            {
                IProvider<T> provider = binding.ProviderInstance;
                ISet<InjectionPoint> injectionPoints = binding.InjectionPoints;
                Initializable<IProvider<T>> initializable = processor.initializer.RequestInjection<IProvider<T>>(processor.injector, provider, command.Source, injectionPoints);
                InternalFactory<T> factory = new InternalFactoryToProviderAdapter<T>(initializable, command.Source);
                InternalFactory<T> scopedFactory = Scoping.Scope(command.Key, processor.injector, factory, command.Source, scoping);
                processor.PutBinding(new ProviderInstanceBindingImpl<T>(processor.injector, command.Key, command.Source, scopedFactory, scoping, provider, injectionPoints));
                return null;
            }

            public AsyncVoid Visit(IProviderKeyBinding<T> binding)
            {
                //C#无法转换子类化的泛型参数，因此使用动态对象方式处理
                //Key<IProvider<T>> providerKey = binding.ProviderKey;
                dynamic providerKey = binding.ProviderKey;
                BoundProviderFactory<T> boundProviderFactory = new BoundProviderFactory<T>(processor.injector, providerKey, command.Source);
                processor.creation += boundProviderFactory.Notify;
                InternalFactory<T> scopedFactory = Scoping.Scope(command.Key, processor.injector, (InternalFactory<T>)boundProviderFactory, command.Source, scoping);
                processor.PutBinding(new LinkedProviderBindingImpl<T>(processor.injector, command.Key, command.Source, scopedFactory, scoping, providerKey));
                return null;
            }

            public AsyncVoid Visit(ILinkedKeyBinding<T> binding)
            {
                Key linkedKey = binding.LinkedKey;
                if (command.Key.Equals(linkedKey))
                {
                    processor.errors.RecursiveBinding();
                }

                FactoryProxy<T> factory = new FactoryProxy<T>(processor.injector, command.Key, linkedKey, command.Source);
                processor.creation += factory.Notify;
                InternalFactory<T> scopedFactory
                    = Scoping.Scope(command.Key, processor.injector, factory, command.Source, scoping);
                processor.PutBinding(new LinkedBindingImpl<T>(processor.injector, command.Key, command.Source, scopedFactory, scoping, linkedKey));
                return null;
            }

            public AsyncVoid Visit(IUntargettedBinding<T> untargetted)
            {
                // Error: Missing implementation.
                // Example: bind(Date.class).annotatedWith(Red.class);
                // We can't assume abstract types aren't injectable. They may have an
                // @ImplementedBy annotation or something.
                if (command.Key.AttributeType != null)
                {
                    processor.errors.MissingImplementation(command.Key);
                    processor.PutBinding(processor.InvalidBinding(processor.injector, command.Key, command.Source));
                    return null;
                }

                // This cast is safe after the preceeding check.
                try
                {
                    BindingImpl<T> binding = processor.injector.CreateUninitializedBinding(command.Key, scoping, command.Source, processor.errors, false);
                    ScheduleInitialization(binding);
                    processor.PutBinding(binding);
                }
                catch (ErrorsException e)
                {
                    processor.errors.Merge(e.Errors);
                    processor.PutBinding(processor.InvalidBinding(processor.injector, command.Key, command.Source));
                }

                return null;
            }

            public AsyncVoid Visit(IExposedBinding<T> binding) 
            {
                throw new ArgumentException("Cannot apply a non-module element");
            }

            public AsyncVoid Visit(IConvertedConstantBinding<T> binding)
            {
                throw new ArgumentException("Cannot apply a non-module element");
            }

            public AsyncVoid Visit(IProviderBinding binding)
            {
                throw new ArgumentException("Cannot apply a non-module element");
            }

            private void ScheduleInitialization(BindingImpl<T> binding)
            {
                processor.uninitializedBindings.Add(() =>
                {
                    try
                    {
                        binding.Injector.InitializeBinding(binding, processor.errors.WithSource(command.Source));
                    }
                    catch (ErrorsException e)
                    {
                        processor.errors.Merge(e.Errors);
                    }
                });
            }

        }

        public override bool Visit(IPrivateElements privateElements)
        {
            foreach (dynamic key in privateElements.GetExposedKeys())
            {
                BindExposed(privateElements, key);
            }
            return false; // leave the private elements for the PrivateElementsProcessor to handle
        }

        private void BindExposed<T>(IPrivateElements privateElements, Key<T> key)
        {
            ExposedKeyFactory<T> exposedKeyFactory = new ExposedKeyFactory<T>(key, privateElements);
            this.creation += exposedKeyFactory.Notify;
            this.PutBinding(new ExposedBindingImpl<T>(
                injector, privateElements.GetExposedSource(key), key, exposedKeyFactory, privateElements));
        }

        private void ValidateKey<T>(object source, Key<T> key)
        {
            Attributes.CheckForMisplacedScopeAnnotations(key.GetType(), source, errors);
        }

        UntargettedBindingImpl<T> InvalidBinding<T>(InjectorImpl injector, Key<T> key, object source)
        {
            return new UntargettedBindingImpl<T>(injector, key, source);
        }

        public void InitializeBindings()
        {
            foreach (Run initializer in uninitializedBindings)
            {
                initializer();
            }
        }

        public void RunCreationListeners()
        {
            if (creation != null)
                creation(errors);
        }

        private void PutBinding<T>(BindingImpl<T> binding)
        {
            Key<T> key = binding.Key;
            if (FORBIDDEN_TYPES.Contains(key.GetType()))
            {
                errors.CannotBindToGuiceType(key.GetType().Name);
                return;
            }

            BindingImpl<T> original = injector.state.GetExplicitBinding(key);
            if (original != null)
            {
                try
                {
                    if (!IsOkayDuplicate(original, binding, injector.state))
                    {
                        errors.BindingAlreadySet(key, original.Source);
                        return;
                    }
                }
                catch (Exception t)
                {
                    errors.ErrorCheckingDuplicateBinding(key, original.Source, t);
                    return;
                }
            }

            // prevent the parent from creating a JIT binding for this key
            injector.state.Parent.Blacklist(key);
            injector.state.PutBinding(key, binding);
        }

        /// <summary>
        /// 如果一个绑定暴露另一个绑定或者两个绑定被认为是重复的
        /// （Bindings#AreDuplicates(BindingImpl, BindingImpl)），则我们将容许两个重复的绑定
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="original">父注入器的binding（一个暴露的binding的候选）</param>
        /// <param name="binding">要检查的binding</param>
        /// <param name="state"></param>
        /// <returns></returns>
        private bool IsOkayDuplicate<T>(BindingImpl<T> original, BindingImpl<T> binding, IState state)
        {
            if (original is ExposedBindingImpl<T>)
            {
                ExposedBindingImpl<T> exposed = (ExposedBindingImpl<T>)original;
                InjectorImpl exposedFrom = (InjectorImpl)exposed.PrivateElements.Injector;
                return (exposedFrom == binding.Injector);
            }
            else
            {
                original = (BindingImpl<T>)state.GetExplicitBindingsThisLevel<T>()[binding.Key];
                // If no original at this level, the original was on a parent, and we don't
                // allow deduplication between parents & children.
                if (original == null)
                {
                    return false;
                }
                else
                {
                    return original.Equals(binding);
                }
            }
        }

        // It's unfortunate that we have to maintain a blacklist of specific
        // classes, but we can't easily block the whole package because of
        // all our unit tests.
        private static readonly ISet<Type> FORBIDDEN_TYPES = ImmutableSet<Type>.Of(
            typeof(AbstractModule),
            typeof(IBinder),
            typeof(IBinding<>),
            typeof(Injector),
            typeof(Key<>),
            typeof(IMembersInjector<>),
            typeof(IModule),
            typeof(IProvider<>),
            typeof(IScope));

    }
}
